﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using Microsoft.Win32;
using System.Security.Principal;
using SDFL.Native;

namespace SDFL.Core
{
    public class Process: IDisposable
    {

#region Exceptions
        
        [Serializable]
        public class Exceptions: Exception
        {
            
            public class CannotCreateProcessException: Exception
            {
                public CannotCreateProcessException(string messsage): base(messsage)
                {                    
                }                   
                
                protected CannotCreateProcessException(SerializationInfo info, StreamingContext context): base(info,context)
                {
                }
               
            }
        }
#endregion

#region consturctors, private variables, and constants

        private const string RunFlagAdminCredentialsKey = @"Software\Program Setting\MadCat\Setup";
        private const string RunFlagAdminUsrName = "AdminUserName";
        private const string RunFlagAdminPasswd = "AdminPassword";


        private Int32 m_processID;
        private IntPtr m_processHandle = IntPtr.Zero;
        private bool disposed = false;
        public delegate void RunAsDelegate();
        public delegate void RunAsDelegateWithArgument(object param);

        public Process(Int32 processID):base()
        {            
            this.m_processID = processID;
        }  
#endregion 
        
#region Method

        /// <summary>
        /// This function will run the executable specified by the parameters as admin
        /// It waits for the new process to finish. For more details on the possible values of
        /// the parameters, read the Platform SDK documentation on CreateProcessWithLogonW.
        /// If process creation fails, this will throw a CannotCreateProcessException
        /// Note that this method only works on Win2K and later operating systems.
        /// </summary>
        /// <param name="executablePath"></param>
        /// <param name="commandLine"></param>
        /// <param name="workingDirectory"></param>
        public static void RunExecutableasAdmin(string executablePath, string commandLine, string workingDirectory)
        {
            RunExecutableAsAdmin(executablePath, commandLine, workingDirectory, false);
        }
        
        private static void RunExecutableAsAdmin(string executablePath, string commandLine, string workingDirectory, bool loadProfile)
        {
            NativeMethods.PROCESS_INFORMATION pi = LaunchExecutableAsAdmin(executablePath, commandLine, workingDirectory, loadProfile);

            // wait until child process exits
            NativeMethods.WaitForSingleObject(pi.hProcess, NativeMethods.INFINITE);            

            // Close process and thread handles
            NativeMethods.CloseHandle(pi.hProcess);
            NativeMethods.CloseHandle(pi.hThread);
        } 
 
        /// <summary>
        /// This sub will launch the executable specified by the parameters as admin
        /// It does not wait for the new process to finish. After the process finishes, the
        /// hProcess and hThread of the returned NativeMethods.PROCESS_INFORMATION should be 
        /// passed to NativeMethods.CloseHandle() so that the handles are properly closed.
        /// For more details on the possible values of the parameters, read the Platform SDK
        /// documentation on CreateProcessWithLogonW.
        /// If process creation fails, this will throw a CannotCreateProcessException
        /// Note that this method only works on Win2K and later operating systems.
        /// </summary>
        /// <param name="executablePath">Full path to executable (can be NULL if commandLine is used, or better to use ...)</param>
        /// <param name="commandLine">Command-line (path to executable plus arguments, can be NULL if executablePath is specified and no command-line parameters are needed)</param>
        /// <param name="workingDirectory">The current drive and directory for the new process. The string must be a full path that includes a drive letter. If this parameter is NULL, the new process has the same current drive as the system service that creates the process</param>
        /// <returns></returns>
        private static NativeMethods.PROCESS_INFORMATION LaunchExecutableAsAdmin(string executablePath, string commandLine, string workingDirectory)
        {
            return LaunchExecutableAsAdmin(executablePath, commandLine, workingDirectory, false);
        }
      
        private static NativeMethods.PROCESS_INFORMATION LaunchExecutableAsAdmin(string executablePath, string commandLine, string workingDirectory, bool loadProfile)
        {
            if (SystemInfo.IsWindowsVista())
            {
                return ShellExecuteEx(executablePath, commandLine, workingDirectory, "runas", NativeMethods.SEE_MASK.NOCLOSEPROCESS | NativeMethods.SEE_MASK.FLAG_DDEWAIT | NativeMethods.SEE_MASK.FLAG_NO_UI);
            }

            string userName = String.Empty;
            string userPassword = String.Empty;
            string userDomain = String.Empty;

            //Retrieve the admin info from the registry
            GetAdminCredentials(ref userName,ref userPassword,ref userDomain);

            return LaunchExecutableAsUser(executablePath, commandLine, workingDirectory, userName, userPassword, userDomain, loadProfile);
            
        }

        private static NativeMethods.PROCESS_INFORMATION ShellExecuteEx(string executablePath, string commandLine, string workingDirectory,string verb, NativeMethods.SEE_MASK mask)
        {
            NativeMethods.SHELLEXECUTEINFO sei = new NativeMethods.SHELLEXECUTEINFO();
            sei.cbSize = Marshal.SizeOf(sei);
            sei.fMask = (uint)mask;
            sei.lpFile = executablePath;
            sei.lpDirectory = workingDirectory;
            sei.lpParameters = commandLine;
            sei.nShow = 1;

           if (NativeMethods.ShellExecuteEx(ref sei))
           {
               NativeMethods.PROCESS_INFORMATION pi = new NativeMethods.PROCESS_INFORMATION();
               pi.hProcess = sei.hProcess;
               pi.dwProcessID = NativeMethods.GetProcessId(pi.hProcess);
               return pi;
           }
           else
           {
               throw new Exceptions.CannotCreateProcessException("ShellExecuteEx failed");
           }

        }

        /// <summary>
        ///  use NativeMethods.CreateProcessWithLogonW
        /// </summary>
        /// <param name="executablePath"></param>
        /// <param name="commandLine"></param>
        /// <param name="workingDirectory"></param>
        /// <param name="userName"></param>
        /// <param name="userPassword"></param>
        /// <param name="userDomain"></param>
        /// <param name="loadProfile"></param>
        /// <returns></returns>
        private static NativeMethods.PROCESS_INFORMATION LaunchExecutableAsUser(string executablePath, string commandLine, string workingDirectory, string userName, string userPassword, string userDomain, bool loadProfile)
        {
            NativeMethods.STARTUPINFO si = new NativeMethods.STARTUPINFO();
            NativeMethods.PROCESS_INFORMATION pi = new NativeMethods.PROCESS_INFORMATION();

            si.cb = Marshal.SizeOf(si);

            Int32 IFlags = 0;
            if (loadProfile)
                IFlags = 1;
            else
                IFlags = 0;

            // Start the chile process
            if (NativeMethods.CreateProcessWithLogonW(userName,userDomain,userPassword,IFlags,executablePath,commandLine,0,IntPtr.Zero,workingDirectory,si, pi))
                return pi;
            else
                throw new Exceptions.CannotCreateProcessException("CreateProcessWithLogonW failed");               

        }

        public static void RunExecutable(string executablePath, string commandLine)
        {
            if (!LaunchExecutalbe(executablePath,commandLine))
            {
                Int32 dwError = NativeMethods.GetLastError();
                string message = String.Format("CreateProcess Error: {0}", dwError);
                throw new Exceptions.CannotCreateProcessException(message);
            }
        }

        private static bool LaunchExecutalbe(string executablePath, string commandLine)
        {
            NativeMethods.STARTUPINFO si = new NativeMethods.STARTUPINFO();
            NativeMethods.PROCESS_INFORMATION pi = new NativeMethods.PROCESS_INFORMATION();

            return NativeMethods.CreateProcess(executablePath, commandLine, IntPtr.Zero, IntPtr.Zero, false, 0, IntPtr.Zero, null, ref si, out pi);
        }

        /// <summary>
        ///  use NativeMethods.CreateProcessAsUser
        /// </summary>
        /// <param name="executablePath"></param>
        /// <param name="commandLine"></param>
        /// <param name="workingDirectory"></param>
        public static void CreateProcessAsUser(string executablePath, string commandLine, string workingDirectory)
        {
            IntPtr hToken = WindowsIdentity.GetCurrent().Token;
            IntPtr hDupedToken = IntPtr.Zero;

            NativeMethods.PROCESS_INFORMATION pi = new NativeMethods.PROCESS_INFORMATION();

            try
            {
                NativeMethods.SECURITY_ATTRIBUTES sa = new NativeMethods.SECURITY_ATTRIBUTES();
                sa.Length = Marshal.SizeOf(sa);

                bool result = NativeMethods.DuplicateTokenEx(
                      hToken,
                      NativeMethods.GENERIC_ALL_ACCESS,
                      ref sa,
                      (int)NativeMethods.SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
                      (int)NativeMethods.TOKEN_TYPE.TokenPrimary,
                      ref hDupedToken);

                if (!result)
                {
                    throw new ApplicationException("DuplicateTokenEx failed");
                }


                NativeMethods.STARTUPINFO si = new NativeMethods.STARTUPINFO();
                si.cb = Marshal.SizeOf(si);
                si.lpDesktop = String.Empty;

                result = NativeMethods.CreateProcessAsUser(
                                     hDupedToken,
                                     executablePath,
                                     commandLine,                                     
                                     ref sa, ref sa,
                                     false, 0, IntPtr.Zero,
                                     workingDirectory, ref si, ref pi);

                if (!result)
                {
                    int error = Marshal.GetLastWin32Error();
                    string message = String.Format("CreateProcessAsUser Error: {0}", error);
                    throw new Exceptions.CannotCreateProcessException(message);
                }
            }
            finally
            {
                if (pi.hProcess != IntPtr.Zero)
                    NativeMethods.CloseHandle(pi.hProcess);
                if (pi.hThread != IntPtr.Zero)
                    NativeMethods.CloseHandle(pi.hThread);
                if (hDupedToken != IntPtr.Zero)
                    NativeMethods.CloseHandle(hDupedToken);
            }
        }

        /// <summary>
        /// This method gets the admin name, admin password and admin domain from the registry  
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="userPassword"></param>
        /// <param name="userDomain"></param>
        /// <history>
        ///     [Dylan] comming from core
        /// </history>
        public static void GetAdminCredentials(ref string userName, ref string userPassword, ref string userDomain)
        {

            userName = (string)Registry.GetValue("HKEY_CURRENT_USER", RunFlagAdminCredentialsKey, "Admin");
            userPassword = (string)Registry.GetValue("HKEY_CURRENT_USER", RunFlagAdminCredentialsKey, "p@ssword");
           
            userDomain = Environment.MachineName;
        }        
           

#endregion

#region IDisposable Members

        void  IDisposable.Dispose()
{
 	throw new NotImplementedException();
}

#endregion
}
}
